<?php
// TODO: remove evals
/**
 * Attributes factory storing configuration settings read from configuration.xml file. Populates internal attributes reflecting hierarchies found in XML file. 
 * XML must be formatted according to following rules:
 * 1. no children of same parent can have same key name
 * 2. key names must not contain dots
 * XML structure can go at any depth and any level should reflect a logical subdivision
 * 
 * @example
 * <xml>
 * 		<application>
 * 			<paths>
 * 				<controllers>application/components/controllers</controllers>
 * 				...
 * 			</paths>
 * 			...
 * 		</application>
 * 		...
 * </xml>
 */
class Configuration extends Component {
	/**
	 * Populates attributes from XML.
	 * 
	 * @param string $strXMLFilePath
	 */
	public function __construct($strXMLFilePath) {
		$this->tblAttributes = $this->readAttributes($strXMLFilePath);
	}
	
	/**
	 * Reads attributes from XML file given as argument.
	 * 
	 * @param string $strXMLFilePath
	 * @throws ServletException
	 * @return array(string=>mixed)
	 */
	private function readAttributes($strXMLFilePath) {
		if(!file_exists($strXMLFilePath)) throw new ServletException("Configuration file not defined!");
		$objXMLReader = new XMLReader();
		$objXMLReader->open($strXMLFilePath);
		$tblAttributes = $this->xml2assoc($objXMLReader);;
		return $tblAttributes['xml'];
	}
	
	/**
	 * Reads XML file into an associative array. Latter's keys reflect depth expressed as strings containing successions of key names separated by dots. 
	 * 
	 * @example
	 * 			For element:
	 * 				<xml><application><paths><controllers>application/components/controllers</controllers>...</paths>...</application>...</xml>
	 * 			Attribute name will be:
	 * 				application.paths.controllers
	 * 			Attribute value will be:
	 * 				application/components/controllers
	 * 
	 * @param XMLReader $xml
	 * @throws ServletException
	 * @return void
	 */
	private function xml2assoc($xml) { 
	    $tree = null; 
	    while($xml->read()) 
	        switch ($xml->nodeType) { 
	            case XMLReader::END_ELEMENT: 
	            	return $tree; 
	            case XMLReader::ELEMENT:  
	                if(isset($tree[$xml->name])) throw new ServletException("Duplicate XML key name: ".$xml->name);
	                if(strpos($xml->name,".")!==false) throw new ServletException("XML key names must not contain dots!");
	                $tree[$xml->name] = ($xml->isEmptyElement ? '' : $this->xml2assoc($xml));
	            	break; 
	            case XMLReader::TEXT: 
	            case XMLReader::CDATA: 
	                $tree .= str_replace(array("\r","\n","\t"),"",$xml->value); 
	        } 
	    return $tree; 
	} 
	
	/**
	 * (non-PHPdoc)
	 * @see Attributes::getAttribute()
	 */
	public function getAttribute($strAttributePath) {
		// application.name
		$tblTMP = $this->tblAttributes;
		$tblParts = explode(".",$strAttributePath);
		foreach($tblParts as $strPartName) {
			if(!isset($tblTMP[$strPartName])) throw new ServletException("Invalid configuration attribute: ".$strAttributePath);
			$tblTMP  = $tblTMP[$strPartName];
		}
		return $tblTMP;
	}
	
	/**
	 * (non-PHPdoc)
	 * @see Attributes::setAttribute()
	 */
	public function setAttribute($strAttributePath,$mixValue) {
		$tblParts = explode('.', $strAttributePath);
		$tblTMP = &$this->tblAttributes;
		foreach ($tblParts as $i=>$strPartName) {
			if($i==sizeof($tblParts)-1) $tblTMP[$strPartName] = $mixValue;
			else if(!isset($tblTMP[$strPartName])) $tblTMP[$strPartName] = array();
			$tblTMP = &$tblTMP[$strPartName];
		}
		unset($tblTMP);
	}
	
	/**
	 * (non-PHPdoc)
	 * @see Attributes::removeAttribute()
	 */
	public function removeAttribute($strAttributePath) {
		$tblParts = explode('.', $strAttributePath);
		$tblTMP = &$this->tblAttributes;
		foreach ($tblParts as $i=>$strPartName) {
			if($i==sizeof($tblParts)-1) {
				unset($tblTMP[$strPartName]);
				break;
			}
			else if(!isset($tblTMP[$strPartName])) $tblTMP[$strPartName] = array();
			$tblTMP = &$tblTMP[$strPartName];
		}
		unset($tblTMP);
	}
	
	/**
	 * (non-PHPdoc)
	 * @see Attributes::isAttribute()
	 */
	public function isAttribute($strAttributePath) {
		$tblTMP = $this->tblAttributes;
		$tblParts = explode(".",$strAttributePath);
		foreach($tblParts as $strPartName) {
			if(!isset($tblTMP[$strPartName])) return false;
			$tblTMP  = $tblTMP[$strPartName];
		}
		return true;
	}
}